import glfw
import glm
from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np
import ctypes
import time
import random

vertexShaderScorce = '''
    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;

    out vec3 ourColor;
    uniform vec3 offset;
    uniform bool color_shift;
    uniform vec3 new_color;

    void main()
    {
        float clampedX = clamp(aPos.x + offset.x, -1.0, 1.0);
        float clampedY = clamp(aPos.y + offset.y, -1.0, 1.0);
        float clampedZ = clamp(aPos.z + offset.z, -1.0, 1.0);
        gl_Position = vec4(clampedX, clampedY, clampedZ, 1.0);
        
        float new_r = 0.0;
        float new_g = 0.0;
        float new_b = 0.0;
        if (color_shift) {
            if (aColor.r == 0.0){
                new_r = new_color.r;
            } 
            if (aColor.g == 0.0){
                new_g = new_color.g;
            }
            if (aColor.b == 0.0){
                new_b = new_color.b;
            }
            ourColor = vec3(new_r, new_g, new_b);
        } else {
            ourColor = aColor;
        }
    }
'''

fragmentShaderSource = '''
    #version 330 core
    out vec4 FragColor;
    in vec3 ourColor;
    void main()
    {
        FragColor = vec4(ourColor, 1.0);
    }
'''


def framebuffer_size_callback(window, width, height):
    glViewport(0, 0, width, height)

def processInput(window):
    if glfw.get_key(window, glfw.KEY_ESCAPE) == glfw.PRESS:
        glfw.set_window_should_close(window, True)

if __name__ == "__main__":
    if not glfw.init():
        print("Failed to initialize GLFW")
        raise Exception("Failed to initialize GLFW")
    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)

    window = glfw.create_window(800, 600, "Hello World", None, None)
    if not window:
        print("Failed to create GLFW window")
        glfw.terminate()
        raise Exception("Failed to create GLFW window")
    
    glfw.make_context_current(window)
    glfw.set_framebuffer_size_callback(window, framebuffer_size_callback)

    # vertexShader
    vertexShader = glCreateShader(GL_VERTEX_SHADER)
    glShaderSource(vertexShader, vertexShaderScorce)
    glCompileShader(vertexShader)
    if not glGetShaderiv(vertexShader, GL_COMPILE_STATUS):
        print("Failed to compile vertex shader")
        raise Exception("Failed to compile vertex shader")

    # FragmentShader
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
    glShaderSource(fragmentShader, fragmentShaderSource)
    glCompileShader(fragmentShader)
    if not glGetShaderiv(fragmentShader, GL_COMPILE_STATUS):
        print("Failed to compile fragment shader")
        raise Exception("Failed to compile fragment shader")
    
    # link shaders
    shaderProgram = glCreateProgram()
    glAttachShader(shaderProgram, vertexShader)
    glAttachShader(shaderProgram, fragmentShader)
    glLinkProgram(shaderProgram)
    if not glGetProgramiv(shaderProgram, GL_LINK_STATUS):
        glGetProgramInfoLog(shaderProgram)
        print("Failed to link shader program")
        raise Exception("Failed to link shader program")

    glDeleteShader(vertexShader)
    glDeleteShader(fragmentShader)



    vertices = np.array([
        # position          # color
        0.3, -0.3, 0.0, 1.0, 0.0, 0.0,
        -0.3, -0.3, 0.0, 0.0, 1.0, 0.0,
        0.0, 0.3, 0.0, 0.0, 0.0, 1.0,
    ], dtype=np.float32)

    VAO = glGenVertexArrays(1)
    VBO = glGenBuffers(1)
    glBindVertexArray(VAO)

    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * vertices.itemsize, ctypes.c_void_p(0))
    glEnableVertexAttribArray(0)

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * vertices.itemsize, ctypes.c_void_p(3 * vertices.itemsize))
    glEnableVertexAttribArray(1)

    glBindVertexArray(VAO)
    glUseProgram(shaderProgram)

    i = 0
    j = 0
    while not glfw.window_should_close(window):
        glfw.poll_events()
        processInput(window)

        glClearColor(0.2, 0.3, 0.3, 1.0)
        glClear(GL_COLOR_BUFFER_BIT)

        # pos offset
        x_offset = np.sin(time.time()) * 0.7
        y_offset = np.cos(time.time()) * 0.7
        vertexColorOffset = glGetUniformLocation(shaderProgram, "offset")
        glUniform3f(vertexColorOffset, x_offset, y_offset, 0.0)

        # color offset
        colorShift = glGetUniformLocation(shaderProgram, "color_shift")
        glUniform1i(colorShift, 1)
        # set new color
        newColor = glGetUniformLocation(shaderProgram, "new_color")
        r = np.sin(time.time()) * 0.5 + 0.5
        g = np.cos(time.time()) * 0.5 + 0.5
        b = np.sin(time.time() + np.pi) * 0.5 + 0.5
        glUniform3f(newColor, r, g, b)
        
        glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
        glEnableVertexAttribArray(0)
        glEnableVertexAttribArray(1)
        # glBindVertexArray(VAO)
        glDrawArrays(GL_TRIANGLES, 0, 3)
        glfw.swap_buffers(window)
        glfw.poll_events()
        time.sleep(0.1)
    
    glDeleteBuffers(1, [VBO])
    glDeleteVertexArrays(1, [VAO])
    glDeleteProgram(shaderProgram)
    glfw.terminate()